package com.example.blog.module.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.blog.module.component.MyElasticsearchRepository;
import com.example.blog.module.entity.*;
import com.example.blog.module.entity.VO.*;
import com.example.blog.module.mapper.ArticleMapper;
import com.example.blog.module.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.blog.module.utils.BaseContent;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;


/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author zjx
 * @since 2022-11-22
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {

    private final ArticleMapper articleMapper;

    private final TagService tagService;

    private final SysUserService sysUserService;

    private final ArticleTagService articleTagService;

    private final CategoryService categoryService;

//    private final RocketMQProducer rocketMQProducer;

    private final RedisTemplate<String, String> redisTemplate;

    private final ArticleLikeService articleLikeService;

    private final FollowService followService;

    private final MyElasticsearchRepository myElasticsearchRepository;

    private final ElasticsearchRestTemplate elasticsearchRestTemplate;

    private final ThreadService threadService;

    /**
     * 1、分页查询article数据库表
     */
    @Override
    public Result listArticle(PageParams pageParams) {
        Long userId = BaseContent.get().getId();

        if (pageParams.getPage() == null){
            pageParams.setPage(1);
        }
        if (pageParams.getPageSize() == null){
            pageParams.setPageSize(10);
        }

        //搜索 热度/最新
        if (pageParams.getSearch()!=null){
            NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
            queryBuilder.withQuery(QueryBuilders.multiMatchQuery(pageParams.getSearch(),"title", "summary", "content"));
            queryBuilder.withPageable(PageRequest.of(pageParams.getPage(), pageParams.getPageSize()));
            queryBuilder.withSort(SortBuilders.fieldSort("hotness").order(SortOrder.DESC));

            SearchHits<Article> searchHits = elasticsearchRestTemplate.search(queryBuilder.build(), Article.class);
            List<Article> articles = new ArrayList<>();
            for (SearchHit<Article> searchHit : searchHits) {
                articles.add(searchHit.getContent());
            }
            List<ArticleVo> articleVoList =copyList(articles,true,true);
            return Result.success(articleVoList);
        }

        Page<Article> page = new Page<>(pageParams.getPage(), pageParams.getPageSize());
        QueryWrapper<Article> queryWrapper = new QueryWrapper<>();

        //是否只查找自己的
        if (pageParams.getIsMe()!=null && pageParams.getIsMe()){
            queryWrapper.eq("author_id",userId);
        }

        //是否查找年 月
        if (pageParams.getYear()!=null && pageParams.getMonth()!=null){
            queryWrapper.apply("YEAR(create_date) = {0}", pageParams.getYear());
            queryWrapper.apply("MONTH(create_date) = {0}", pageParams.getMonth());
        }


        //是否查找分类
        if (pageParams.getCategoryId()!=null) {
            queryWrapper.eq("category_id",pageParams.getCategoryId());
        }

        //是否查找标签
        List<Long> articleIdList = new ArrayList<>();
        if(pageParams.getTagId()!=null){
            LambdaQueryWrapper<ArticleTag> articleTagLambdaQueryWrapper = new LambdaQueryWrapper<>();
            articleTagLambdaQueryWrapper.eq(ArticleTag::getTagId,pageParams.getTagId());
            List<ArticleTag> articleTags = articleTagService.list(articleTagLambdaQueryWrapper);
            for (ArticleTag articleTag : articleTags) {
                articleIdList.add(articleTag.getArticleId());
            }
            if (articleTags.size() > 0) {
                queryWrapper.in("id",articleIdList);
            }

        }

        //是否置顶进行排序
        queryWrapper.orderByDesc("weight","create_date");
        Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper);

        //分页查询用法
        List<Article> records = articlePage.getRecords();

        // 要返回我们定义的vo数据，就是对应的前端数据，不应该只返回现在的数据需要进一步进行处理
        List<ArticleVo> articleVoList =copyList(records,true,true);
        return Result.success(articleVoList);
    }

    //"eop的作用是对应copyList，集合之间的copy分解成集合元素之间的copy
    private ArticleVo copy(Article article,boolean isTag,boolean isAuthor){
        ArticleVo articleVo = new ArticleVo();
        BeanUtils.copyProperties(article, articleVo);
        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        articleVo.setCreateDate(df.format(article.getCreateDate()));
        //并不是所有的接口都需要标签和作者信息
        if(isTag){
            Long articleId = article.getId();
            articleVo.setTags(tagService.findTagsByIds(articleTagService.getIdsByArticleId(articleId)));
        }
        if (isAuthor) {
            //拿到作者id
            Long authorId = article.getAuthorId();

            UserVo author = new UserVo();
            SysUser sysUser = sysUserService.findUserById(authorId);
            author.setAvatar(sysUser.getAvatar());
            author.setNickname(sysUser.getNickname());
            author.setId(sysUser.getId());
            author.setIsFollow(followService.followStatus(sysUser.getId()));
            articleVo.setAuthor(author);
        }

        //查询点赞数
        articleVo.setLikeCounts(articleLikeService.findEntityLikeCount("article",articleVo.getId()));

        //查询当前用户是否点赞该文章
        articleVo.setIsLike(articleLikeService.likeStatus("article",articleVo.getId()));

        return articleVo;

    }

    @Override
    public Result hotArticle(int limit) {
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(Article::getViewCounts);
        queryWrapper.select(Article::getId,Article::getTitle);
        //"limit"字待串后要加空格，不要忘记加空格，不然会把数据拼到一起
        queryWrapper.last("limit "+limit);
        //select id,title from article order by view_counts desc limt 5
        List<Article> articles = articleMapper.selectList(queryWrapper);
        //返回vo对象
        return Result.success(articles);
    }


    @Override
    public Result newArticles(int limit) {
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(Article::getCreateDate);
        queryWrapper.select(Article::getId,Article::getTitle);
        queryWrapper.last("limit "+limit);
        //select id,title from article order by create_date desc limit 5
        List<Article> articles = articleMapper.selectList(queryWrapper);

        return Result.success(articles);
    }


    @Override
    public Result findArticleById(Long articleId) {
        Article article = this.articleMapper.selectById(articleId);

        // 将观看人数存入 redis ，自增，定时固化到数据库
        String redisKey ="VIEW_COUNT"+articleId.toString();
        String redisValue = redisTemplate.opsForValue().get(redisKey);
        if (StringUtils.isEmpty(redisValue)){
            redisTemplate.opsForValue().set(redisKey,article.getViewCounts()+"");
        }

        //将 INCR 命令放到一个事务中，并使用 WATCH 命令监视相关的 key，在执行事务之前检查 key 的值是否发生变化。如果值发生变化，那么说明有其他客户端修改了这个值，需要取消当前事务并重试
        while (true) {
            redisTemplate.setEnableTransactionSupport(true);
            redisTemplate.watch(redisKey);
            String value = redisTemplate.opsForValue().get(redisKey);
            redisTemplate.multi();
            Long newValue = redisTemplate.opsForValue().increment(redisKey);
            List<Object> res = redisTemplate.exec();
            if (res != null){
                break;
            }
        }


        ArticleVo articleVo = copy(article, true, true,true,true);
        return Result.success(articleVo);
    }

    private List<ArticleVo> copyList(List<Article> records,boolean isTag,boolean isAuthor) {

        List<ArticleVo> articleVoList = new ArrayList<>();

        for (Article record : records) {
            articleVoList.add(copy(record,isTag,isAuthor));
        }
        return articleVoList;

    }

    private List<ArticleVo> copyList(List<Article> records, boolean isTag, boolean isAuthor,boolean isBody) {
        List<ArticleVo> articleVoList = new ArrayList<>();
        for (Article record : records) {
            articleVoList.add(copy(record,isTag,isAuthor,isBody,false));
        }
        return articleVoList;
    }
    private List<ArticleVo> copyList(List<Article> records, boolean isTag, boolean isAuthor,boolean isBody,boolean isCategory) {
        List<ArticleVo> articleVoList = new ArrayList<>();
        for (Article record : records) {
            articleVoList.add(copy(record,isTag,isAuthor,isBody,isCategory));
        }
        return articleVoList;
    }

    private ArticleVo copy(Article article, boolean isTag, boolean isAuthor, boolean isBody,boolean isCategory){
        ArticleVo articleVo = new ArticleVo();
        articleVo.setId(article.getId());
        BeanUtils.copyProperties(article,articleVo);
        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        articleVo.setCreateDate(df.format(article.getCreateDate()));
        //并不是所有的接口 都需要标签 ，作者信息
        if (isTag){
            Long articleId = article.getId();
            articleVo.setTags(tagService.findTagsByIds(articleTagService.getIdsByArticleId(articleId)));
        }
        if (isAuthor){
            Long authorId = article.getAuthorId();

            UserVo author = new UserVo();
            SysUser sysUser = sysUserService.findUserById(authorId);
            author.setAvatar(sysUser.getAvatar());
            author.setNickname(sysUser.getNickname());
            author.setId(sysUser.getId());
            author.setIsFollow(followService.followStatus(sysUser.getId()));
            articleVo.setAuthor(author);
        }
        if (isBody){
            articleVo.setBody(article.getContent());
        }
        if (isCategory){
            Long categoryId = article.getCategoryId();
            articleVo.setCategory(categoryService.findCategoryById(categoryId));
        }

        //查询点赞数
        articleVo.setLikeCounts(articleLikeService.findEntityLikeCount("article",articleVo.getId()));

        //查询当前用户是否点赞该文章
        articleVo.setIsLike(articleLikeService.likeStatus("article",articleVo.getId()));

        return articleVo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result publish(ArticleParam articleParam) {
        //注意想要拿到数据必须将接口加入拦截器
        SysUser sysUser = BaseContent.get();

        /**
         * 1. 发布文章 目的 构建Article对象
         * 2. 作者id  当前的登录用户
         * 3. 标签  要将标签加入到 关联列表当中
         * 4. body 内容存储 article bodyId
         */
        Article article = new Article();
        article.setAuthorId(sysUser.getId());
        article.setCategoryId(articleParam.getCategory().getId());
        article.setCreateDate(LocalDateTime.now());
        article.setCommentCounts(0);
        article.setSummary(articleParam.getSummary());
        article.setTitle(articleParam.getTitle());
        article.setViewCounts(0);
        article.setWeight(0);
        article.setHotness((double) 0);
        article.setContent(articleParam.getBody().getContent());
        article.setContentHtml(articleParam.getBody().getContentHtml());
        //"insert后主键会自动'set到实体的ID字段。所以只需要"get id()就好
        this.articleMapper.insert(article);

        //tags
        List<Tag> tags = articleParam.getTags();
        if (tags != null) {
            for (Tag tag : tags) {
                ArticleTag articleTag = new ArticleTag();
                articleTag.setArticleId(article.getId());
                articleTag.setTagId(tag.getId());
                this.articleTagService.save(articleTag);
            }
        }

        //存入es
        myElasticsearchRepository.save(article);

        ArticleVo articleVo = new ArticleVo();
        articleVo.setId(article.getId());
        return Result.success(articleVo);
    }


    @Override
    public Result personArchives() {
        /*
        文章归档
         */
        Long userId =BaseContent.get().getId();
        List<Archive> archivesList = articleMapper.personArchives(userId);
        return Result.success(archivesList);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result updateById(ArticleParam articleParam) {

        Article article = articleMapper.selectById(articleParam.getId());
        article.setContent(articleParam.getBody().getContent());
        article.setContentHtml(articleParam.getBody().getContentHtml());
        articleMapper.updateById(article);

        //es异步更新
        threadService.updateEsArticle(article);

        //消息提醒 文章 更新了 缓存也要更新
//        rocketMQProducer.sendMessage("blog-update-article",articleParam.getId());
        return Result.success("");
    }
}
